
#pragma comment(lib, "glfwdll.lib")
#pragma comment(lib, "glew32.lib")
#pragma comment(lib, "opengl32.lib")
//#pragma comment (lib, "glu32.lib")


//#include <GL\glu.h>

#include <iostream>
#include <string>
#include <sstream>


#include <gl\glew.h>
#include <GL\glfw.h>

#include "Shapes3D.h"
#include "PastyCam.h"
#include "Texture.h"
#include "FrameBufferObject.h"

#include <cmath>
#include <assert.h>

#include "siut/simd/Vec3f.hpp"
#include "siut/simd/Vec4f.hpp"
#include "siut/simd/Mat4f.hpp"
using siut::simd::Vec3f;
using siut::simd::Vec4f;
using siut::simd::Mat4f;

#include <stack>

#define MYPUSH model_view_matrix.push( model_view_matrix.top() );
#define MYPOP	model_view_matrix.pop();

#define PIOVER180 0.01745329251994329576923690768489f

int mouseX, mouseY;
int oldMouseX, oldMouseY;
int fps;
int frames;
double fpsTimeBase;
PastyCam cam;
PastyCam renderCam;
Texture *fboTexture;
FrameBufferObject fbo1;

std::stack<Mat4f> model_view_matrix;
std::stack<Mat4f> projection_matrix;

int xres = 800;
int yres = 600;

bool Init()
{
	if( glfwInit() != GL_TRUE) return false;

	glfwOpenWindowHint(GLFW_WINDOW_NO_RESIZE, GL_TRUE);
	//glfwOpenWindowHint(GLFW_FSAA_SAMPLES, 4);
	glfwOpenWindowHint(GLFW_OPENGL_VERSION_MAJOR, 2);
	glfwOpenWindowHint(GLFW_OPENGL_VERSION_MINOR, 1);

	if( glfwOpenWindow(xres,yres,0,0,0,0,0,0,GLFW_WINDOW) != GL_TRUE ) return false;

	GLenum err = glewInit();
	if (GLEW_OK != err)
	{
		printf("GLEW ERROR: %s\n", glewGetErrorString(err));
		return false;
	}
	if( !GLEW_VERSION_2_1)
	{
		printf("You must have OpenGL 2.1 support to run this program!\n");
		return false;
	}
	
	/* If you use GLEW, comment these out, as GLEW seems
		to have some weird issues with forward-compatible core profiles.
		Otherwise you should always use forward-compatible core profiles
		in order to get proper error-reporting from OpenGL if you use outdated
		functionality that makes performance suffer.
		FUNCTION-POINTERS YOURSELF */

	//glfwOpenWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
	//glfwOpenWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE)
	return true;
}

void drawCamera(Mat4f *modelview, Mat4f *camFrame)
{
		MYPUSH
		{
			Mat4f invertMe;
			invertMe = inverse(*camFrame);

			Mat4f transform;
			transform = (*modelview) * (invertMe) ;
			glLoadMatrixf( &transform.v_[0]  );
			glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
			//Shapes3D::CreateBox(0,0,0,1,1,1,1,1);
			Shapes3D::DrawCube(1);
			glTranslatef(0,0,-1);
			//Shapes3D::CreateBox(0,0,0,1,1,1,1,1);
			Shapes3D::DrawCube(.5);
			glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
		}
		MYPOP
}

void drawScene(double time){
		
	  //model_view_matrix.push( model_view_matrix.top() ); //glPushMatrix();
		MYPUSH
	  translate(model_view_matrix.top(), 0,-1,0); // glTranslatef(0,-1,0);
		glLoadMatrixf(model_view_matrix.top().v_);
		Shapes3D::DrawWireGrid(50,50,50);
		MYPOP

		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

		MYPUSH
	  rotateY(model_view_matrix.top(), (float)time*100*PIOVER180); // glTranslatef(0,-1,0);
		glLoadMatrixf(model_view_matrix.top().v_);
		Shapes3D::FastTeapot(6,1);
		MYPOP

		MYPUSH
		translate(model_view_matrix.top(), 6,0,0);
		glLoadMatrixf(model_view_matrix.top().v_);
		Shapes3D::DrawSphere(1,18,18);
		MYPOP

		MYPUSH
		rotateX(model_view_matrix.top(), (float)time*100*PIOVER180);
		translate(model_view_matrix.top(), -3,-1.5f,0);
		glLoadMatrixf(model_view_matrix.top().v_);
		Shapes3D::DrawCylinder(.5f,3.f,18,0);
		MYPOP

		MYPUSH
		rotateY(model_view_matrix.top(), (float)time*100*PIOVER180);
		glLoadMatrixf(model_view_matrix.top().v_);
		Shapes3D::DrawCone(.5f, 2.0f, 12);
		MYPOP

		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}

void updateCamera(double dt)
{
	glfwGetMousePos(&mouseX, &mouseY);

  float mouseSpeedX, mouseSpeedY;
  if(glfwGetMouseButton(GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS)
  {
  	float mouseSpd = 0.01f;
  	mouseSpeedX = mouseSpd*float(mouseX-oldMouseX);
    mouseSpeedY = mouseSpd*float(mouseY-oldMouseY);
  }
  else
  {
    mouseSpeedX = 0;
    mouseSpeedY = 0;
  }

	float vx = 0.0f;
	float vz = 0.0f;
	if( !glfwGetKey('C') )
	{
		float f = 5.0f;
		vx = f * float(glfwGetKey('A') - glfwGetKey('D') ) * (float)dt;
		vz = f * float(glfwGetKey('W') - glfwGetKey('S')) * (float)dt;
	}
	cam.update( -vx,
  			  0.0f,
  			  -vz,
  			  -mouseSpeedX,
  			  mouseSpeedY
  );
  
Mat4f mv;
	for(int i=0; i<16; i++)
		mv.v_[i] = (float)cam.getMatrix()[i];

	model_view_matrix.top() = mv;
  glLoadMatrixf( cam.getMatrix() );
	
}

void updateRenderCamera(double dt)
{
	glfwGetMousePos(&mouseX, &mouseY);

  float mouseSpeedX, mouseSpeedY;
  if(glfwGetMouseButton(GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS)
  {
  	float mouseSpd = 0.01f;
  	mouseSpeedX = mouseSpd*float(mouseX-oldMouseX);
    mouseSpeedY = mouseSpd*float(mouseY-oldMouseY);
  }
  else
  {
    mouseSpeedX = 0;
    mouseSpeedY = 0;
  }
  oldMouseX = mouseX;
  oldMouseY = mouseY;
  
	float vx = 0.0f;
	float vy = 0.0f;
	float vz = 0.0f;

	// Move if C is down
	if( glfwGetKey('C') )
	{
		float f = 5.0f;
		vx = f * float(glfwGetKey('A') - glfwGetKey('D') ) * (float)dt;
		vz = f * float(glfwGetKey('W') - glfwGetKey('S')) * (float)dt;
		vy = f * float(glfwGetKey('E') - glfwGetKey('R')) * (float)dt;
	}

	renderCam.update( -vx,
  			  -vy,
  			  -vz,
  			  -mouseSpeedX,
  			  mouseSpeedY
  );
  
  //glLoadMatrixd( cam.getMatrix() );
	
}

// http://steinsoft.net/index.php?site=Programming/Code%20Snippets/OpenGL/gluperspective
void glusPerspective( GLdouble fovy, GLdouble aspect, GLdouble near, GLdouble far )
{
    GLdouble half_height = near * tan( fovy * M_PI / 360.0 );
    GLdouble half_width = half_height * aspect;

    glFrustum( -half_width, half_width, -half_height, half_height, near, far );
} 

int main()
{
	if( !Init() ) return 1;
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);
	//glEnable(GL_CULL_FACE);
	//glCullFace(GL_BACK);


	fboTexture = new Texture(512,512);
	fbo1.init();
	fbo1.enable();
	fbo1.attachTexture( fboTexture->getId() );
	fbo1.checkStatus();
	fbo1.disable();

	cam.posx = 20;
	cam.posy = 10;
	cam.posz = 0;
	cam.vang = (float) (20 * M_PI/180.0);
	cam.hang = (float) M_PI/2.0;

	renderCam.posy = 2;
	renderCam.posz = 5;

	glfwGetMousePos(&oldMouseX, &oldMouseY);
	fps=0;
	frames=0;
	fpsTimeBase = glfwGetTime() + 1;

	std::stringstream stream;

	Mat4f proj;
	setPerspectiveMatrix(proj, 90.0f, static_cast<float>(xres)/static_cast<float>(yres), 1.f, 1000.0f );
	projection_matrix.push( proj );

	Mat4f mv;
	setIdentity(mv);
	model_view_matrix.push(mv);

	float time = (float)glfwGetTime();
	float oldtim = (float)glfwGetTime();
	float dt = 0.01f;
	while(glfwGetWindowParam(GLFW_OPENED))
	{
		float now = (float)glfwGetTime();
		dt = now - time;
		time = now;
		//renderCam.hang = cos( glm.time*M_PI ) / 2.0;
		//renderCam.vang = sin( glm.time*M_PI ) / 4.0;

		glClearColor(0,0,0,1);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		setPerspectiveMatrix(proj, 90.0f, static_cast<float>(xres)/static_cast<float>(yres), 0.1f, 1000.0f );
		projection_matrix.top() = proj;
		glMatrixMode(GL_PROJECTION);
		glLoadMatrixf( projection_matrix.top().v_ );
		glMatrixMode(GL_MODELVIEW);

		updateCamera(dt);
		updateRenderCamera(dt);
		oldMouseX = mouseX;
		oldMouseY = mouseY;

		////////////////////////////////////////
		Mat4f mat_modelview;
		Mat4f renderCamActorFrame;
		for(int i=0; i<16; i++)
			mat_modelview.v_[i] = (float)cam.getMatrix()[i];
		for(int i=0; i<16; i++)
			renderCamActorFrame.v_[i] = (float)renderCam.getMatrix()[i];

		drawScene(time);
		drawCamera(&mat_modelview, &renderCamActorFrame);	
		
		MYPUSH
		//glPushMatrix();
		{
			copyInto(model_view_matrix.top(),renderCamActorFrame); // glLoadMatrixf

			fbo1.enable();
			glViewport(0, 0, fboTexture->getWidth(),fboTexture->getHeight() );		
			glClearColor(.2f,.2f,.2f,1.f);
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
			
			drawScene(time);
			fbo1.disable();
		}	
		MYPOP
		//glPopMatrix();
		glViewport(0, 0, xres, yres);	

		glEnable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, fboTexture->getId() );
		glGenerateMipmap(GL_TEXTURE_2D);

		MYPUSH
		translate(model_view_matrix.top(), -20, 4,0);
		rotateY(model_view_matrix.top(), 90*PIOVER180);
		glLoadMatrixf(model_view_matrix.top().v_);
		Shapes3D::DrawQuad(5);
		//glRotated( 100*glm.time, 0,1,0);
		//Shapes3D::CreateBox(0,0,0,3,3,3,1,1);
		MYPOP

		///////////////
		glBindTexture(GL_TEXTURE_2D, 0 );
		glDisable(GL_TEXTURE_2D);
		
		if( time > fpsTimeBase)
		{
			fps = frames;
			frames = 0;
			fpsTimeBase = time + 1;

			stream << "press and hold C + wasd to control render cam... "<< fps << " fps";
			glfwSetWindowTitle( stream.str().c_str() );
			stream.str("");
		}
		frames++;

		glfwSwapBuffers();
	}

	return 0;
}